const frappe = require('frappe');
const Observable = require('frappe/utils/observable');
const triggerEvent = (name) => frappe.events.trigger(`http:${name}`);

module.exports = class HTTPClient extends Observable {
  constructor({ server, protocol = 'http' }) {
    super();

    this.server = server;
    this.protocol = protocol;
    frappe.config.serverURL = this.getURL();

    // if the backend is http, then always client!
    frappe.isServer = false;

    this.initTypeMap();
  }

  connect() {}

  async insert(doctype, doc) {
    doc.doctype = doctype;
    let filesToUpload = this.getFilesToUpload(doc);
    let url = this.getURL('/api/resource', doctype);

    const responseDoc = await this.fetch(url, {
      method: 'POST',
      body: JSON.stringify(doc),
    });

    await this.uploadFilesAndUpdateDoc(filesToUpload, doctype, responseDoc);

    return responseDoc;
  }

  async get(doctype, name) {
    name = encodeURIComponent(name);
    let url = this.getURL('/api/resource', doctype, name);
    return await this.fetch(url, {
      method: 'GET',
      headers: this.getHeaders(),
    });
  }

  async getAll({ doctype, fields, filters, start, limit, sortBy, order }) {
    let url = this.getURL('/api/resource', doctype);

    url =
      url +
      '?' +
      frappe.getQueryString({
        fields: JSON.stringify(fields),
        filters: JSON.stringify(filters),
        start: start,
        limit: limit,
        sortBy: sortBy,
        order: order,
      });

    return await this.fetch(url, {
      method: 'GET',
    });
  }

  async update(doctype, doc) {
    doc.doctype = doctype;
    let filesToUpload = this.getFilesToUpload(doc);
    let url = this.getURL('/api/resource', doctype, doc.name);

    const responseDoc = await this.fetch(url, {
      method: 'PUT',
      body: JSON.stringify(doc),
    });

    await this.uploadFilesAndUpdateDoc(filesToUpload, doctype, responseDoc);

    return responseDoc;
  }

  async delete(doctype, name) {
    let url = this.getURL('/api/resource', doctype, name);

    return await this.fetch(url, {
      method: 'DELETE',
    });
  }

  async deleteMany(doctype, names) {
    let url = this.getURL('/api/resource', doctype);

    return await this.fetch(url, {
      method: 'DELETE',
      body: JSON.stringify(names),
    });
  }

  async exists(doctype, name) {
    return (await this.getValue(doctype, name, 'name')) ? true : false;
  }

  async getValue(doctype, name, fieldname) {
    let url = this.getURL('/api/resource', doctype, name, fieldname);

    return (
      await this.fetch(url, {
        method: 'GET',
      })
    ).value;
  }

  async fetch(url, args) {
    triggerEvent('ajaxStart');

    args.headers = this.getHeaders();
    let response = await frappe.fetch(url, args);

    triggerEvent('ajaxStop');

    if (response.status === 200) {
      let data = await response.json();
      return data;
    }

    if (response.status === 401) {
      triggerEvent('unauthorized');
    }

    throw Error(await response.text());
  }

  getFilesToUpload(doc) {
    const meta = frappe.getMeta(doc.doctype);
    const fileFields = meta.getFieldsWith({ fieldtype: 'File' });
    const filesToUpload = [];

    if (fileFields.length > 0) {
      fileFields.forEach((df) => {
        const files = doc[df.fieldname] || [];
        if (files.length) {
          filesToUpload.push({
            fieldname: df.fieldname,
            files: files,
          });
        }
        delete doc[df.fieldname];
      });
    }

    return filesToUpload;
  }

  async uploadFilesAndUpdateDoc(filesToUpload, doctype, doc) {
    if (filesToUpload.length > 0) {
      // upload files
      for (const fileToUpload of filesToUpload) {
        const files = await this.uploadFiles(
          fileToUpload.files,
          doctype,
          doc.name,
          fileToUpload.fieldname
        );
        doc[fileToUpload.fieldname] = files[0].name;
      }
    }
  }

  async uploadFiles(fileList, doctype, name, fieldname) {
    let url = this.getURL('/api/upload', doctype, name, fieldname);

    let formData = new FormData();
    for (const file of fileList) {
      formData.append('files', file, file.name);
    }

    let response = await frappe.fetch(url, {
      method: 'POST',
      body: formData,
    });

    const data = await response.json();
    if (response.status !== 200) {
      throw Error(data.error);
    }
    return data;
  }

  getURL(...parts) {
    return this.protocol + '://' + this.server + (parts || []).join('/');
  }

  getHeaders() {
    const headers = {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    };
    if (frappe.session && frappe.session.token) {
      headers.token = frappe.session.token;
    }
    return headers;
  }

  initTypeMap() {
    this.typeMap = {
      AutoComplete: true,
      Currency: true,
      Int: true,
      Float: true,
      Percent: true,
      Check: true,
      'Small Text': true,
      'Long Text': true,
      Code: true,
      'Text Editor': true,
      Date: true,
      Datetime: true,
      Time: true,
      Text: true,
      Data: true,
      Link: true,
      DynamicLink: true,
      Password: true,
      Select: true,
      'Read Only': true,
      File: true,
      Attach: true,
      'Attach Image': true,
      Signature: true,
      Color: true,
      Barcode: true,
      Geolocation: true,
    };
  }

  close() {}
};
